﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Threading;
using Model;

namespace ReportBrowser.ViewModel
{
    internal class MemberViewModel : LanguageElementViewModelBase
    {
        #region Nested

        class Session
        {
            public Dictionary<Member, MemberViewModel> CallerInstances =
                new Dictionary<Member, MemberViewModel>();
            public Dictionary<Member, MemberViewModel> CalleeInstances =
                new Dictionary<Member, MemberViewModel>();
            public Dictionary<Member, MemberViewModel> TruncatedAsCallerInstances =
                new Dictionary<Member, MemberViewModel>();
            public Dictionary<Member, MemberViewModel> TruncatedAsCalleeInstances =
                new Dictionary<Member, MemberViewModel>();
        }

        #endregion Nested

        #region Fields

        private static Dictionary<object, Session> sSessions = new Dictionary<object, Session>();

        private static object sLastSessionKey;

        #endregion Fields

        #region Lifecycle

        static MemberViewModel()
        {
            MessageViewModel.InstanceSelected += new EventHandler(MessageViewModel_InstanceSelected);
#if THREADING
            sThreading = true;
#endif
#if GETCALLGRAPH
            sGetCallGraph = true;
#endif
        }

        public MemberViewModel(Member model, bool isTruncatedAsCaller = false, bool isTruncatedAsCallee = false)
            : base(isTruncatedAsCaller, isTruncatedAsCallee)
        {
            this.Model = model;
            this.Callees = new ObservableCollection<MemberViewModel>();
            this.Callers = new ObservableCollection<MemberViewModel>();
        }

        internal static void StartNewSession(object sessionKey)
        {
            if (sSessions.ContainsKey(sessionKey) == false)
                sSessions.Add(sessionKey, new Session());
            sLastSessionKey = sessionKey;
            ClearInstances(sLastSessionKey);
        }

        private static MemberViewModel GetInstance(Member model, bool isCaller,
            bool isTruncatedAsCaller, bool isTruncatedAsCallee)
        {
            return GetInstance(sLastSessionKey, model, isCaller, isTruncatedAsCaller, isTruncatedAsCallee);
        }

        public static MemberViewModel GetInstance(object sessionKey, Member model, bool isCaller,
            bool isTruncatedAsCaller, bool isTruncatedAsCallee)
        {
            Session session;
            try
            {
                session = sSessions[sessionKey];
            }
            catch (KeyNotFoundException ex)
            {
                throw new ArgumentException("A session with the given session key does not exist!", "sessionKey", ex);
            }
            if (isCaller == true && isTruncatedAsCaller == false && isTruncatedAsCallee == false)
            {
                if (session.CallerInstances.ContainsKey(model) == false)
                    session.CallerInstances.Add(model, new MemberViewModel(model));
                return session.CallerInstances[model];
            }
            else if (isCaller == false && isTruncatedAsCaller == false && isTruncatedAsCallee == false)
            {
                if (session.CalleeInstances.ContainsKey(model) == false)
                    session.CalleeInstances.Add(model, new MemberViewModel(model));
                return session.CalleeInstances[model];
            }
            else if (isTruncatedAsCaller == true && isTruncatedAsCallee == false)
            {
                if (session.TruncatedAsCallerInstances.ContainsKey(model) == false)
                    session.TruncatedAsCallerInstances.Add(
                        model,
                        new MemberViewModel(model, isTruncatedAsCaller, isTruncatedAsCallee));
                return session.TruncatedAsCallerInstances[model];
            }
            else if (isTruncatedAsCaller == false && isTruncatedAsCallee == true)
            {
                if (session.TruncatedAsCalleeInstances.ContainsKey(model) == false)
                    session.TruncatedAsCalleeInstances.Add(
                        model,
                        new MemberViewModel(model, isTruncatedAsCaller, isTruncatedAsCallee));
                return session.TruncatedAsCalleeInstances[model];
            }
            else
                throw new ArgumentException();
        }

        public static bool IsInstantiated(object sessionKey, Member model, bool isCaller,
            bool isTruncatedAsCaller, bool isTruncatedAsCallee)
        {
            Session session;
            try
            {
                session = sSessions[sessionKey];
            }
            catch (KeyNotFoundException ex)
            {

                throw new ArgumentException("A session with the given session key does not exist!", "sessionKey", ex);
            }
            if (isCaller == true && isTruncatedAsCaller == false && isTruncatedAsCallee == false)
                return session.CallerInstances.ContainsKey(model);
            else if (isCaller == false && isTruncatedAsCaller == false && isTruncatedAsCallee == false)
                return session.CalleeInstances.ContainsKey(model);
            else if (isTruncatedAsCaller == true && isTruncatedAsCallee == false)
                return session.TruncatedAsCallerInstances.ContainsKey(model);
            else if (isTruncatedAsCaller == false && isTruncatedAsCallee == true)
                return session.TruncatedAsCalleeInstances.ContainsKey(model);
            else
                throw new ArgumentException();
        }

        private static AutoResetEvent sEvent2 = new AutoResetEvent(true);
        internal static void ClearInstances(object keyOfSessionNotToClear = null)
        {
            Debug.WriteLineIf(sThreading, "ClearInstances called from thread " + Thread.CurrentThread.ManagedThreadId.ToString());

            Session sessionNotToClear = null;
            if (keyOfSessionNotToClear != null)
                sSessions.TryGetValue(keyOfSessionNotToClear, out sessionNotToClear);
            foreach (var key in sSessions.Keys)
            {
                var session = sSessions[key];
                ClearInstancesHelper(ref session.CallerInstances);
                ClearInstancesHelper(ref session.CallerInstances);
                ClearInstancesHelper(ref session.TruncatedAsCallerInstances);
                ClearInstancesHelper(ref session.TruncatedAsCalleeInstances);
            }
            sSessions.Clear();
            if (sessionNotToClear != null)
                sSessions[keyOfSessionNotToClear] = sessionNotToClear;
        }

        private static void ClearInstancesHelper(ref Dictionary<Member, MemberViewModel> instances)
        {
            if (instances.Count > 0)
            {
                foreach (var instance in instances)
                {
                    instance.Value.pCallers.Clear();
                    instance.Value.pCallees.Clear();
                    //instance.Value.OnPropertyChanged("Callers");
                    //instance.Value.OnPropertyChanged("Callees");
                }
                instances = new Dictionary<Member, MemberViewModel>();
            }
        }

        #endregion Lifecycle

        #region Properties

        public Member Model { get; private set; }

        private bool mIsCallersInitialized = false;
        private ObservableCollection<MemberViewModel> pCallers;
        public ObservableCollection<MemberViewModel> Callers
        {
            get
            {
                //sEvent2.WaitOne();
                //sEvent.WaitOne(100);
                Debug.WriteLineIf(sThreading, "get_Callers called from thread " + Thread.CurrentThread.ManagedThreadId.ToString());
                if (mIsCallersInitialized == false && this.IsTruncatedAsCaller == false)
                {
                    foreach (var item in this.Model.CallGraphEdge_MemberDependency_Callee)
                    {

                        if (item.CallerMember != null)
                        {
                            MemberViewModel mvm;
                            if (IsOnCycle(item.CallerMember, asCaller: /*true*/false) == false)
                                mvm = GetInstance(item.CallerMember, isCaller: true,
                                    isTruncatedAsCaller: false, isTruncatedAsCallee: false);
                            else
                                mvm = GetInstance(item.CallerMember, isCaller: true,
                                    isTruncatedAsCaller: true, isTruncatedAsCallee: false);
                            pCallers.Add(mvm);
                        }
                        else
                            Debug.WriteLineIf(sGetCallGraph, "(item in CallGraphEdge_MemberDependency_Callee).CallerMember == null");
                    }
                    mIsCallersInitialized = true;
                }
                return pCallers;
            }
            private set
            {
                if (value != pCallers)
                {
                    pCallers = value;
                    OnPropertyChanged("Callers");
                }
            }
        }

        private bool mIsCalleesInitialized = false;
        private ObservableCollection<MemberViewModel> pCallees;
        public ObservableCollection<MemberViewModel> Callees
        {
            get
            {
                Debug.WriteLineIf(sThreading, "get_Callees called from thread " + Thread.CurrentThread.ManagedThreadId.ToString());
                //sEvent2.WaitOne();
                //sEvent.WaitOne(100);
                if (mIsCalleesInitialized == false && this.IsTruncatedAsCallee == false)
                {
                    foreach (var item in this.Model.CallGraphEdge_MemberDependency_Caller)
                    {

                        if (item.CalleeMember != null)
                        {
                            MemberViewModel mvm;
                            if (IsOnCycle(item.CalleeMember, asCaller: /*false*/true) == false)
                                mvm = GetInstance(item.CalleeMember, isCaller: false,
                                    isTruncatedAsCallee: false, isTruncatedAsCaller: false);
                            else
                                mvm = GetInstance(item.CalleeMember, isCaller: false,
                                    isTruncatedAsCallee: true, isTruncatedAsCaller: false);
                            pCallees.Add(mvm);
                        }
                        else
                            Debug.WriteLineIf(sGetCallGraph, "(item in CallGraphEdge_MemberDependency_Caller).CalleeMember == null");
                    }
                    mIsCalleesInitialized = true;
                }
                return pCallees;
            }
            private set
            {
                if (value != pCallees)
                {
                    pCallees = value;
                    OnPropertyChanged("Callees");
                }
            }
        }

        #endregion Properties

        #region Methods

        private bool IsOnCycle(Member member, bool asCaller)
        {
            HashSet<Member> gray = new HashSet<Member>();
            HashSet<Member> black = new HashSet<Member>();
            Debug.WriteLineIf(sGetCallGraph, "IsOnCycle: member=" + member.Name + " asCaller=" + asCaller.ToString());
            List<Member> path = DFS(member, asCaller, ref gray, ref black);
            //if (path != null)
            //    for (int i = 1; i < path.Count; i++)
            //    {
            //        if (path[i].Equals(path[0]) == true)
            //            return false;
            //        else if (path[i].Equals(member))
            //            return true;
            //    }
            //return false;
            if (path != null)
                Debug.WriteLineIf(sGetCallGraph, "path==<" + SetToString(path) + ">");
            return path != null;
        }

        private List<Member> DFS(Member member, bool asCaller,
            ref HashSet<Member> gray, ref HashSet<Member> black)
        {
            Debug.Indent();
            gray.Add(member);
            Debug.WriteLineIf(sGetCallGraph, "gray: {" + SetToString(gray) + "}");
            EntityCollection<CallGraphEdge_MemberDependency> neighbours;
            if (asCaller == true)
                neighbours = member.CallGraphEdge_MemberDependency_Caller;
            else
                neighbours = member.CallGraphEdge_MemberDependency_Callee;
            Debug.Indent();
            foreach (var item in neighbours)
            {
                Member neighbour;
                if (asCaller == true)
                    neighbour = item.CalleeMember;
                else
                    neighbour = item.CallerMember;
                if ((asCaller == true
                    && (MemberViewModel.IsInstantiated(sLastSessionKey, neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: false, isTruncatedAsCallee: false) == true
                            || MemberViewModel.IsInstantiated(sLastSessionKey, neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: true, isTruncatedAsCallee: false) == true))
                    || (asCaller == false
                        && (MemberViewModel.IsInstantiated(sLastSessionKey, neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: false, isTruncatedAsCallee: false) == true
                            || MemberViewModel.IsInstantiated(sLastSessionKey, neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: false, isTruncatedAsCallee: true) == true)))
                {
                    Debug.WriteLineIf(sGetCallGraph, "neighb: " + neighbour.Name);
                    if (gray.Contains(neighbour) == false && black.Contains(neighbour) == false)//white
                    {
                        Debug.WriteLineIf(sGetCallGraph, " is WHITE");
                        List<Member> path = DFS(neighbour, asCaller, ref gray, ref black);
                        if (path != null)
                        {
                            Debug.WriteLineIf(sGetCallGraph, "but path=={" + SetToString(path) + "}!=null");
                            path.Add(neighbour);
                            Debug.Unindent();
                            Debug.Unindent();
                            return path;
                        }
                    }
                    else if (gray.Contains(neighbour) == true && black.Contains(neighbour) == false)//gray
                    {
                        Debug.WriteLineIf(sGetCallGraph, " is GRAY");
                        Debug.Unindent();
                        Debug.Unindent();
                        return new List<Member>() { neighbour };
                    }
                }
            }
            Debug.Unindent();
            gray.Remove(member);
            black.Add(member);
            Debug.WriteLineIf(sGetCallGraph, "gray: {" + SetToString(gray) + "}");
            Debug.WriteLineIf(sGetCallGraph, "black:{" + SetToString(black) + "}");
            Debug.Unindent();
            return null;
        }

        private string SetToString(IEnumerable<Member> set)
        {
            string s = "";
            foreach (var item in set)
            {
                s += item.Name + ", ";
            }
            return s.TrimEnd(new char[] { ' ', ',' });
        }

        #endregion Methods

        #region EventHandlers

        static AutoResetEvent sEvent = new AutoResetEvent(false);

        static void MessageViewModel_InstanceSelected(object sender, EventArgs e)
        {
            //sEvent2.Reset();
            Debug.WriteLineIf(sThreading, "MessageViewModel_InstanceSelected called from thread " + Thread.CurrentThread.ManagedThreadId.ToString());
            //TODO: itt új thread indítása
            //Thread t = new Thread(new ThreadStart(ClearInstances));
            //t.IsBackground = true;
            //t.Start();
            ClearInstances();
            //sEvent2.Set();
            //sEvent.Set();
        }

        #endregion EventHandlers

    }
}
